#include "BasisSwap.h"
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/iborcoupon.hpp>
#include <ql/cashflows/cashflowvectors.hpp>
#include <ql/cashflows/cashflows.hpp>
#include <ql/cashflows/couponpricer.hpp>
#include <ql/indexes/iborindex.hpp>
#include <ql/termstructures/yieldtermstructure.hpp>

namespace QuantLib {

    BasisSwap::BasisSwap(
                     Type type,
                     Real nominal,
					 const Schedule& floatingSchedule,
					 const boost::shared_ptr<IborIndex>& iborIndex,
                     Spread spread,
                     const DayCounter& floatingDayCount,
					 const Schedule& floatingSchedule2,
                     const boost::shared_ptr<IborIndex>& iborIndex2,
                     Spread spread2,
					 const DayCounter& floatingDayCount2 )
					 :Swap(2), 
						type_(type), 
						nominal_(nominal),
						floatingSchedule_(floatingSchedule), 
						iborIndex_(iborIndex), 
						spread_(spread),
						floatingSchedule2_(floatingSchedule2), 
						iborIndex2_(iborIndex2), 
						spread2_(spread2)
						{
							paymentConvention_ = floatingSchedule_.businessDayConvention();
							paymentConvention2_ = floatingSchedule2_.businessDayConvention();

							legs_[0] = IborLeg(floatingSchedule_, iborIndex_)
								.withNotionals(nominal_)
								.withPaymentDayCounter(floatingDayCount_)
								.withPaymentAdjustment(paymentConvention_)
								.withSpreads(spread_);

							legs_[1] = IborLeg(floatingSchedule2_, iborIndex2_)
								.withNotionals(nominal_)
								.withPaymentDayCounter(floatingDayCount2_)
								.withPaymentAdjustment(paymentConvention2_)
								.withSpreads(spread2_);

							for (Leg::const_iterator i = legs_[0].begin(); i < legs_[0].end(); ++i)
								registerWith(*i);

							for (Leg::const_iterator i = legs_[1].begin(); i < legs_[1].end(); ++i)
								registerWith(*i);

							switch (type_) {
							  case Payer:
								payer_[0] = -1.0;
								payer_[1] = +1.0;
								break;
							  case Receiver:
								payer_[0] = +1.0;
								payer_[1] = -1.0;
								break;
							  default:
								QL_FAIL("Unknown basis-swap type");
						}
    }

    void BasisSwap::setupArguments(PricingEngine::arguments* args) const {

        Swap::setupArguments(args);

        BasisSwap::arguments* arguments =
            dynamic_cast<BasisSwap::arguments*>(args);

        if (!arguments)  // it's a swap engine...
            return;

        arguments->type = type_;
        arguments->nominal = nominal_;

        const Leg& floatingCoupons = floatingLeg();

        arguments->floatingResetDates = arguments->floatingPayDates =
            arguments->floatingFixingDates =
            std::vector<Date>(floatingCoupons.size());
        arguments->floatingAccrualTimes =
            std::vector<Time>(floatingCoupons.size());
        arguments->floatingSpreads =
            std::vector<Spread>(floatingCoupons.size());
        arguments->floatingCoupons = std::vector<Real>(floatingCoupons.size());
        for (Size i=0; i<floatingCoupons.size(); ++i) {
            boost::shared_ptr<IborCoupon> coupon =
            boost::dynamic_pointer_cast<IborCoupon>(floatingCoupons[i]);

            arguments->floatingResetDates[i] = coupon->accrualStartDate();
            arguments->floatingPayDates[i] = coupon->date();

            arguments->floatingFixingDates[i] = coupon->fixingDate();
            arguments->floatingAccrualTimes[i] = coupon->accrualPeriod();
            arguments->floatingSpreads[i] = coupon->spread();
            try {
                arguments->floatingCoupons[i] = coupon->amount();
            } catch (Error&) {
                arguments->floatingCoupons[i] = Null<Real>();
            }
        }

		const Leg& floatingCoupons2 = floatingLeg2();

        arguments->floatingResetDates2 = arguments->floatingPayDates2 =
            arguments->floatingFixingDates2 =
            std::vector<Date>(floatingCoupons2.size());
        arguments->floatingAccrualTimes2 =
            std::vector<Time>(floatingCoupons2.size());
        arguments->floatingSpreads2 =
            std::vector<Spread>(floatingCoupons2.size());
        arguments->floatingCoupons2 = std::vector<Real>(floatingCoupons2.size());
        for (Size i=0; i<floatingCoupons2.size(); ++i) {
            boost::shared_ptr<IborCoupon> coupon =
            boost::dynamic_pointer_cast<IborCoupon>(floatingCoupons2[i]);

            arguments->floatingResetDates2[i] = coupon->accrualStartDate();
            arguments->floatingPayDates2[i] = coupon->date();

            arguments->floatingFixingDates2[i] = coupon->fixingDate();
            arguments->floatingAccrualTimes2[i] = coupon->accrualPeriod();
            arguments->floatingSpreads2[i] = coupon->spread();
            try {
                arguments->floatingCoupons2[i] = coupon->amount();
            } catch (Error&) {
                arguments->floatingCoupons2[i] = Null<Real>();
            }
        }
    }

    Spread BasisSwap::fairSpread() const {
        calculate();
        QL_REQUIRE(fairSpread_ != Null<Spread>(), "result not available");
        return fairSpread_;
    }

    Real BasisSwap::floatingLegBPS() const {
        calculate();
        QL_REQUIRE(legBPS_[0] != Null<Real>(), "result not available");
        return legBPS_[0];
    }

    Real BasisSwap::floatingLegNPV() const {
        calculate();
        QL_REQUIRE(legNPV_[0] != Null<Real>(), "result not available");
        return legNPV_[0];
    }

	  Spread BasisSwap::fairSpread2() const {
        calculate();
        QL_REQUIRE(fairSpread2_ != Null<Spread>(), "result not available");
        return fairSpread2_;
    }

    Real BasisSwap::floatingLeg2BPS() const {
        calculate();
        QL_REQUIRE(legBPS_[1] != Null<Real>(), "result not available");
        return legBPS_[1];
    }

    Real BasisSwap::floatingLeg2NPV() const {
        calculate();
        QL_REQUIRE(legNPV_[1] != Null<Real>(), "result not available");
        return legNPV_[1];
    }


    void BasisSwap::setupExpired() const {
        Swap::setupExpired();
        legBPS_[0] = legBPS_[1] = 0.0;
        fairSpread2_ = Null<Spread>();
        fairSpread_ = Null<Spread>();
    }

    void BasisSwap::fetchResults(const PricingEngine::results* r) const {
        static const Spread basisPoint = 1.0e-4;

        Swap::fetchResults(r);

        const BasisSwap::results* results =
            dynamic_cast<const BasisSwap::results*>(r);
        if (results) { // might be a swap engine, so no error is thrown
			fairSpread_ = results->fairSpread;
			fairSpread2_ = results->fairSpread2;
        } 
		else {
            fairSpread_ = Null<Spread>();
            fairSpread2_ = Null<Spread>();
        }

        if (fairSpread_ == Null<Spread>()) {
            // ditto
            if (legBPS_[0] != Null<Real>())
                fairSpread_ = spread_ - NPV_/(legBPS_[0]/basisPoint);
        }
        if (fairSpread2_ == Null<Spread>()) {
            // ditto
            if (legBPS_[1] != Null<Real>())
                fairSpread2_ = spread2_ - NPV_/(legBPS_[1]/basisPoint);
        }
    }

    void BasisSwap::arguments::validate() const {
        Swap::arguments::validate();
        QL_REQUIRE(nominal != Null<Real>(), "nominal null or not set");
        QL_REQUIRE(floatingResetDates.size() == floatingPayDates.size(),
                   "number of floatinging start dates different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingFixingDates.size() == floatingPayDates.size(),
                   "number of floatinging fixing dates different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingAccrualTimes.size() == floatingPayDates.size(),
                   "number of floatinging accrual Times different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingSpreads.size() == floatingPayDates.size(),
                   "number of floatinging spreads different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingPayDates.size() == floatingCoupons.size(),
                   "number of floatinging payment dates different from "
                   "number of floatinging coupon amounts");

        QL_REQUIRE(floatingResetDates2.size() == floatingPayDates2.size(),
                   "number of floatinging start dates different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingFixingDates2.size() == floatingPayDates2.size(),
                   "number of floatinging fixing dates different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingAccrualTimes2.size() == floatingPayDates2.size(),
                   "number of floatinging accrual Times different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingSpreads2.size() == floatingPayDates2.size(),
                   "number of floatinging spreads different from "
                   "number of floatinging payment dates");
        QL_REQUIRE(floatingPayDates2.size() == floatingCoupons2.size(),
                   "number of floatinging payment dates different from "
                   "number of floatinging coupon amounts");
    }

    void BasisSwap::results::reset() {
        Swap::results::reset();
        fairSpread = Null<Spread>();
        fairSpread2 = Null<Spread>();
    }

    std::ostream& operator<<(std::ostream& out,
                             BasisSwap::Type t) {
        switch (t) {
          case BasisSwap::Payer:
            return out << "Payer";
          case BasisSwap::Receiver:
            return out << "Receiver";
          default:
            QL_FAIL("unknown BasisSwap::Type(" << Integer(t) << ")");
        }
    }

}
